using System;
using System.Collections.Generic;
using HCore.Unity.Core;
using HCore.Unity.Extensions;
using JetBrains.Annotations;
using Session;
using UnityEngine;

namespace Game.Tile
{
    /// <summary>
    ///     Tile 分组
    ///     <para>这个Mono可能存在大量的对象，不要用 HCore 容器化，会有性能问题。</para>
    /// </summary>
    public class TileGroupVisual : MonoBehaviour
    {
        private readonly HashSet<Vector2Int> mTileControllerHashSet = new();
        private Func<Vector2Int, TileVisual> mGetTileVisualFunc;
        [CanBeNull] private ISession mSession;

        #region Merger TileController

        /// <summary>
        ///     把 Tile 添加到分组中。
        /// </summary>
        /// <param name="tileIndex">Tile 块的索引</param>
        private void AddGroup(Vector2Int tileIndex)
        {
            if (mSession?.Board == null) return;
            // 传入的 Tile 四边的 索引
            var foreSidesTileControllers = Utils.GetForeSidesTileControllers(mSession.Board.NumTileX, mSession.Board.NumTileY, tileIndex);

            // 遍历四边索引，加入到组的环绕索引列表
            foreach (var (direction, index) in foreSidesTileControllers)
                // 如果不在受控组中，则加入到环绕索引列表
                if (!mTileControllerHashSet.Contains(index))
                    mForeSidesTileControllerDict.TryAdd(index, (direction, tileIndex));

            // 把传入的 TileController 剔除环绕索引列表（如在列表中）
            mForeSidesTileControllerDict.Remove(tileIndex);

            // 加入本分组的受控 Tile 列表
            mTileControllerHashSet.Add(tileIndex);
        }

        /// <summary>
        ///     初始化 TileGroup
        ///     <para>设置 TileGroup 的父类</para>
        /// </summary>
        /// <param name="getTileVisualFunc"></param>
        /// <param name="session"></param>
        /// <param name="parent"></param>
        public void Setup(Func<Vector2Int, TileVisual> getTileVisualFunc, ISession session, Transform parent)
        {
            // 调整 TileController 父类和 TileGroup父类
            transform.SetParentAndResetLocalPosition(parent);
            mGetTileVisualFunc = getTileVisualFunc;
            mSession = session;
        }

        /// <summary>
        ///     注册 TileController 到TileGroup
        ///     <para>把 TileController 注册到 TileGroup 里面，统一进行管理</para>
        /// </summary>
        /// <param name="tileVisual"></param>
        public void Register(TileVisual tileVisual)
        {
            if (mSession?.Board == null) return;
            // 已经注册过了，则跳过
            if (mTileControllerHashSet.Contains(tileVisual.Tile.Index)) return;

            // 设置 TileController 的 TileGroup 值 
            tileVisual.TileGroupVisual = this;
            // 把目标索引添加到 TileGroup 中
            AddGroup(tileVisual.Tile.Index);
            // 调整 TileController 父类
            tileVisual.transform.SetParent(transform);

            mSession.Board.TryRemoveControlTile(tileVisual.Tile.Index);
        }

        #endregion

        #region Drag

        /// <summary>
        ///     组的环绕索引列表
        ///     <para>保存 Tile 索引对应的 Tile 四边 索引的数据。方便用来做 Group 和 Group 的拖动合并操作</para>
        /// </summary>
        private readonly Dictionary<Vector2Int, (Tile.Direction, Vector2Int)> mForeSidesTileControllerDict = new();

        /// <summary>
        ///     当按下鼠标按钮时，此变量将存储鼠标位置和瓷砖位置之间的偏移量。
        /// </summary>
        private Vector3 mOffset = Vector3.zero;

        /// <summary>
        ///     当光标在附加此脚本的GameObject上时，拖动鼠标时，会自动调用此方法。
        ///     在此方法中，我们将在拖动时更新瓷砖的位置。
        /// </summary>
        /// <param name="tileVisual"></param>
        public void DoMouseDown(TileVisual tileVisual)
        {
            if (mSession == null) return;
            // 只能在游戏界面
            if (GameManager.GetRouter() != Router.GamePage) return;
            // 准备排序禁止拖动Tile
            if (mSession.IsPreSortingStartedTile) return;
            // 鼠标点击到UI上了
            if (GameUtils.IsPointerOverUI()) return;

            mOffset = transform.position - GameUtils.GetWorldPositionFromScreen();

            // Tile 置顶
            foreach (var vector2Int in mTileControllerHashSet) mGetTileVisualFunc(vector2Int).BringToTop();
        }

        /// <summary>
        ///     在此方法中，我们将在拖动时更新瓷砖的位置。
        /// </summary>
        /// <param name="tileVisual"></param>
        public void DoMouseDrag(TileVisual tileVisual)
        {
            if (mSession == null) return;
            // 只能在游戏界面
            if (GameManager.GetRouter() != Router.GamePage) return;
            // 准备排序禁止拖动Tile
            if (mSession.IsPreSortingStartedTile) return;
            // 鼠标点击到UI上了
            if (GameUtils.IsPointerOverUI()) return;

            transform.position = GameUtils.GetWorldPositionFromScreen() + mOffset;
        }

        /// <summary>
        ///     如果瓷砖离它足够近，我们将处理将瓷砖卡到正确位置。
        /// </summary>
        /// <param name="tileVisual"></param>
        public void DoMouseUp(TileVisual tileVisual)
        {
            if (mSession?.Board == null) return;
            // 只能在游戏界面
            if (GameManager.GetRouter() != Router.GamePage) return;
            // 准备排序禁止拖动Tile
            if (mSession.IsPreSortingStartedTile) return;
            // 鼠标点击到UI上了
            if (GameUtils.IsPointerOverUI()) return;

            // 单位则自动吸附到正确位置，且解散该分组
            var magnitude = (tileVisual.OriginalPos - tileVisual.transform.position).ToVector2().magnitude;
            Debug.Log($"TileGroupVisual --- Min Magnitude: {mSession.Board.MinMagnitude}, Magnitude: {magnitude}, Current Pos: {transform.position}, Original Pos: {tileVisual.OriginalPos}");
            if (magnitude < mSession.Board.MinMagnitude)
            {
                Debug.Log("TileGroupVisual Fixed();");
                foreach (var vector2Int in mTileControllerHashSet) mGetTileVisualFunc(vector2Int).Fixed();
                Destroy(gameObject);
            }
            ///// 处理 TileGroup，这里又分两种情况，一种是当前 Tile 没有 TileGroup ，则把自己合并到对方的 TileGroup（不存在则创建分组）中。
            ///// 第二种是当前移动的就是 TileGroup 则合并两个 TileGroup。
            //// 这里实现第二种，当前 Tile 有 TileGroup。  第一种在 TileController.OnMouseUp() 里面实现。
            else
            {
                foreach (var (sideIndex, (dir, sourceIndex)) in mForeSidesTileControllerDict)
                {
                    // 获取 Tile 之间的补偿值
                    var offset = Utils.GetTileOffset(dir, mSession.Board.TileSize);
                    // 获取四周环绕的 TileVisual
                    var sideTileVisual = mGetTileVisualFunc(sideIndex);
                    // 获取四周环绕 TileVisual 的来源 TileVisual
                    var sourceTileVisual = mGetTileVisualFunc(sourceIndex);
                    // 四周环绕 TileVisual 的偏移点
                    var sideTilePosition = sideTileVisual.transform.position - offset.ToVector3(0);
                    // 计算当前点和目标点的 偏移
                    var magnitudePosition = sideTilePosition - sourceTileVisual.transform.position;

                    // 小于 MinMagnitude 则吸附
                    if (magnitudePosition.ToVector2().magnitude < mSession.Board.MinMagnitude)
                    {
                        Debug.Log($"小于 MinMagnitude 则吸附: Offset: {offset}" +
                                  $"MinMagnitude: {mSession.Board.MinMagnitude}, " +
                                  $"Magnitude: {magnitudePosition}, " +
                                  $"SideTile Position: {sideTilePosition}, " +
                                  $"SourceTileVisual: {sourceTileVisual.transform.position}, " +
                                  $"SideIndex: {sideIndex}, SourceIndex: {sourceIndex}");

                        transform.position += magnitudePosition; // 吸附到目标点

                        // 如果匹配上了。则加入 TileGroup（不存在则创建）
                        var targetTileGroup = Utils.GetOrInitTileGroup(sideTileVisual, mGetTileVisualFunc, mSession, transform.parent);
                        // 向 TileGroup 注册 TileController
                        foreach (var vector2Int in mTileControllerHashSet)
                        {
                            var boardTileController = mGetTileVisualFunc(vector2Int);
                            targetTileGroup.Register(boardTileController);
                        }

                        // 匹配上一个， 就可以直接退出循环。再匹配其它，没有任何意义了。因为已经完成了 Tile 的定位和加入 TileGroup
                        Debug.Log($"TileGroupVisual({transform.position}) Merger;");
                        Destroy(gameObject);
                        break;
                    }
                }
            }
        }

        #endregion
    }
}